#include "StrLib.h"
#include "enc28j60.h"
extern "C"  {
#include "stm32f10x.h"
}

/*
* SPI
*/

volatile U8 CurrentBank = 0;
volatile U16 RxRdPt = 0;


void cEnc28j60::CsLow()
{
    GPIO_ResetBits(ENC28J60_CS_GPIO_PORT, ENC28J60_CS_PIN);
}

void cEnc28j60::CsHigh()
{
    GPIO_SetBits(ENC28J60_CS_GPIO_PORT, ENC28J60_CS_PIN);
}


U8 cEnc28j60::TransferData(U8 iData)
{
    while(SPI_I2S_GetFlagStatus(ENC28J60_SPI, SPI_I2S_FLAG_TXE)==RESET);
    SPI_I2S_SendData(ENC28J60_SPI,iData);

    while(SPI_I2S_GetFlagStatus(ENC28J60_SPI, SPI_I2S_FLAG_RXNE)==RESET);
    return (U8)( SPI_I2S_ReceiveData(ENC28J60_SPI) );
}

U8 cEnc28j60::ReadByte() 
{
    return (TransferData(0xff));
}

void cEnc28j60::WriteByte(U8 iData) 
{
    TransferData(iData);
}

// Generic SPI read command
U8 cEnc28j60::ReadOp(U8 iCmd, U8 iAddr)
{
    U8 temp_U8;

    CsLow();
    WriteByte(iCmd | (iAddr & ENC28J60_ADDR_MASK));
    if(iAddr & 0x80) // throw out dummy byte 
    {
        ReadByte(); // when reading MII/MAC register
    }
    temp_U8 = ReadByte();
    CsHigh();
    return (temp_U8);
}

// Generic SPI write command
void cEnc28j60::WriteOp(U8 iCmd, U8 iAddr, U8 iData)
{
    CsLow();
    WriteByte(iCmd | (iAddr & ENC28J60_ADDR_MASK));
    WriteByte(iData);
    CsHigh();
}

// Initiate software reset
void cEnc28j60::SoftReset()
{
    CsLow();
    WriteByte(ENC28J60_SPI_SC);
    CsHigh();

    CurrentBank = 0;
    cStrLib::DelayMs(10);
}


/*
* Memory access
*/

// Set register bank
void cEnc28j60::SetBlank(U8 iAddr)
{
    U8 bank;

    if( (iAddr & ENC28J60_ADDR_MASK) < ENC28J60_COMMON_CR )
    {
        bank = (iAddr >> 5) & 0x03; //BSEL1|BSEL0=0x03
        if(bank != CurrentBank)
        {
            WriteOp(ENC28J60_SPI_BFC, ECON1, 0x03);
            WriteOp(ENC28J60_SPI_BFS, ECON1, bank);
            CurrentBank = bank;
        }
    }
}

// Read register
U8 cEnc28j60::Rcr(U8 iAddr)
{
    SetBlank(iAddr);
    return (ReadOp(ENC28J60_SPI_RCR, iAddr));
}

// Read register pair
U16 cEnc28j60::Rcr16(U8 iAddr)
{
    SetBlank(iAddr);
    return ReadOp(ENC28J60_SPI_RCR, iAddr) |
        (ReadOp(ENC28J60_SPI_RCR, iAddr+1) << 8);
}

// Write register
void cEnc28j60::Wcr(U8 iAddr, U8 iArg)
{
    SetBlank(iAddr);
    WriteOp(ENC28J60_SPI_WCR, iAddr, iArg);
}

// Write register pair
void cEnc28j60::Wcr16(U8 iAddr, U16 iArg)
{
    SetBlank(iAddr);
    WriteOp(ENC28J60_SPI_WCR, iAddr, (U8)iArg);
    WriteOp(ENC28J60_SPI_WCR, iAddr+1, (U8)(iArg>>8));
}

// Clear bits in register (reg &= ~mask)
void cEnc28j60::Bfc(U8 iAddr, U8 iMask)
{
    SetBlank(iAddr);
    WriteOp(ENC28J60_SPI_BFC, iAddr, iMask);
}

// Set bits in register (reg |= mask)
void cEnc28j60::Bfs(U8 iAddr, U8 iMask)
{
    SetBlank(iAddr);
    WriteOp(ENC28J60_SPI_BFS, iAddr, iMask);
}

// Read Rx/Tx buffer (at ERDPT)
void cEnc28j60::ReadBuffer(U8 *pBuff, U16 iLen)
{
    CsLow();
    WriteByte(ENC28J60_SPI_RBM);
    while(iLen--)
        *(pBuff++) = ReadByte();
    CsHigh();
}

// Write Rx/Tx buffer (at EWRPT)
void cEnc28j60::WriteBuffer(U8 *pBuff, U16 iLen)
{
    CsLow();
    WriteByte(ENC28J60_SPI_WBM);
    while(iLen--)
        WriteByte(*(pBuff++));
    CsHigh();
}

// Read PHY register
U16 cEnc28j60::ReadPhy(U8 iAddr)
{
    Wcr(MIREGADR, iAddr);
    Bfs(MICMD, MICMD_MIIRD);
    while(Rcr(MISTAT) & MISTAT_BUSY)
        ;
    Bfc(MICMD, MICMD_MIIRD);
    return Rcr16(MIRD);
}

// Write PHY register
void cEnc28j60::WritePhy(U8 iAddr, U16 iData)
{
    Wcr(MIREGADR, iAddr);
    Wcr16(MIWR, iData);
    while(Rcr(MISTAT) & MISTAT_BUSY);
}


/*
* Init & packet Rx/Tx
*/

void cEnc28j60::InitReset( U8 *pMacAddr)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    SPI_InitTypeDef SPI_InitStruct;

    RCC_APB2PeriphClockCmd(ENC28J60_SPI_SCK_GPIO_CLK | ENC28J60_SPI_MISO_GPIO_CLK | ENC28J60_SPI_MOSI_GPIO_CLK | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB2PeriphClockCmd(ENC28J60_CS_GPIO_CLK | ENC28J60_RST_GPIO_CLK | ENC28J60_INT_GPIO_CLK, ENABLE);
    if (SPI1 == ENC28J60_SPI)
    {
        RCC_APB2PeriphClockCmd(ENC28J60_SPI_CLK, ENABLE);
    }
    else
    {
        RCC_APB1PeriphClockCmd(ENC28J60_SPI_CLK, ENABLE);
    }

    // MOSI & CLK
    GPIO_InitStruct.GPIO_Pin = ENC28J60_SPI_SCK_PIN | ENC28J60_SPI_MOSI_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ENC28J60_SPI_SCK_GPIO_PORT, &GPIO_InitStruct);

    // MISO
    GPIO_InitStruct.GPIO_Pin = ENC28J60_SPI_MISO_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ENC28J60_SPI_MISO_GPIO_PORT, &GPIO_InitStruct);

    // SS
    GPIO_InitStruct.GPIO_Pin = ENC28J60_CS_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ENC28J60_CS_GPIO_PORT, &GPIO_InitStruct);
    CsHigh();

    // RESET
    GPIO_InitStruct.GPIO_Pin = ENC28J60_RST_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ENC28J60_RST_GPIO_PORT, &GPIO_InitStruct);

    // INT
    GPIO_InitStruct.GPIO_Pin = ENC28J60_INT_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ENC28J60_INT_GPIO_PORT, &GPIO_InitStruct);

    // Initialize SPI
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStruct.SPI_CRCPolynomial = 7;
    SPI_Init(ENC28J60_SPI, &SPI_InitStruct);

    SPI_Cmd(ENC28J60_SPI, ENABLE);

    CsHigh();

    // hardware reset
    GPIO_ResetBits(ENC28J60_RST_GPIO_PORT, ENC28J60_RST_PIN);
    cStrLib::DelayMs(50);
    GPIO_SetBits(ENC28J60_RST_GPIO_PORT, ENC28J60_RST_PIN);
    cStrLib::DelayMs(10);
    
    // Reset ENC28J60
    SoftReset();

    // Setup Rx/Tx buffer
    Wcr16(ERXST, ENC28J60_RXSTART);
    Rcr16(ERXST);
    Wcr16(ERXRDPT, ENC28J60_RXSTART);
    Wcr16(ERXND, ENC28J60_RXEND);
    RxRdPt = ENC28J60_RXSTART;

    // Setup MAC
    Wcr(MACON1, MACON1_TXPAUS| // Enable flow control
        MACON1_RXPAUS|MACON1_MARXEN); // Enable MAC Rx
    Wcr(MACON2, 0); // Clear reset
    Wcr(MACON3, MACON3_PADCFG0| // Enable padding,
        MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX); // Enable crc & frame len chk
    Wcr16(MAMXFL, ENC28J60_MAXFRAME);
    Wcr(MABBIPG, 0x15); // Set inter-frame gap
    Wcr(MAIPGL, 0x12);
    Wcr(MAIPGH, 0x0c);
    Wcr(MAADR5, pMacAddr[0]); // Set MAC address
    Wcr(MAADR4, pMacAddr[1]);
    Wcr(MAADR3, pMacAddr[2]);
    Wcr(MAADR2, pMacAddr[3]);
    Wcr(MAADR1, pMacAddr[4]);
    Wcr(MAADR0, pMacAddr[5]);

    // Setup PHY
    WritePhy(PHCON1, PHCON1_PDPXMD); // Force full-duplex mode
    WritePhy(PHCON2, PHCON2_HDLDIS); // Disable loop back
    WritePhy(PHLCON, PHLCON_LACFG2| // Configure LED ctrl
        PHLCON_LBCFG2|PHLCON_LBCFG1|PHLCON_LBCFG0|
        PHLCON_LFRQ0|PHLCON_STRCH);

    // Enable Rx packets
    Bfs(ECON1, ECON1_RXEN);
    cStrLib::DelayMs(10);
}

void cEnc28j60::SendPacket(U8 *pData, U16 iLen)
{
    while(Rcr(ECON1) & ECON1_TXRTS)
    {
        // TXRTS may not clear - ENC28J60 bug. We must reset
        // transmit logic in cause of Tx error
        if(Rcr(EIR) & EIR_TXERIF)
        {
            Bfs(ECON1, ECON1_TXRST);
            Bfc(ECON1, ECON1_TXRST);
        }
    }

    Wcr16(EWRPT, ENC28J60_TXSTART);
    WriteBuffer((U8*)"\x00", 1);
    WriteBuffer(pData, iLen);

    Wcr16(ETXST, ENC28J60_TXSTART);
    Wcr16(ETXND, ENC28J60_TXSTART + iLen);

    Bfs(ECON1, ECON1_TXRTS); // Request packet send
}

U16 cEnc28j60::ReceivePacket(U8 *pBuff, U16 iMaxLen)
{
    U16 len = 0, rxlen, status, temp;

    if(Rcr(EPKTCNT))
    {
        Wcr16(ERDPT, RxRdPt);

        ReadBuffer((U8*)&RxRdPt, sizeof(RxRdPt));
        ReadBuffer((U8*)&rxlen, sizeof(rxlen));
        ReadBuffer((U8*)&status, sizeof(status));

        if(status & 0x80) //success
        {
            len = rxlen - 4; //throw out crc
            if(len > iMaxLen) len = iMaxLen;
            ReadBuffer(pBuff, len);	
        }

        // Set Rx read pointer to next packet
        temp = (RxRdPt - 1) & ENC28J60_BUFEND;
        Wcr16(ERXRDPT, temp);

        // Decrement packet counter
        Bfs(ECON2, ECON2_PKTDEC);
    }

    return len;
}
